import { $set } from '@feng3d/serialization';
import { Color, CylinderGeometry3D, IcosahedronGeometry3D, Mesh3D, MeshBasicMaterial3D, MeshStandardMaterial3D, Node3D, PerspectiveCamera3D, PlaneGeometry3D, Scene3D, ShadowMaterial3D, StatsComponent, WebGLRenderer3D } from 'feng3d-three';
import GUI from 'lil-gui';
import '../../extension/controls/OrbitControls3D';
import { Brush3D } from '../../three-bvh-csg/core/Brush3D';
import { Evaluator3D } from '../../three-bvh-csg/core/Evaluator3D';
import { ADDITION, INTERSECTION, SUBTRACTION } from '../../three-bvh-csg/imports';

let stats: StatsComponent;
let camera: PerspectiveCamera3D;
let scene: Scene3D;
let renderer: WebGLRenderer3D;
let baseBrush: Brush3D;
let brush: Brush3D;
let core: Brush3D;
let result: Brush3D;
let evaluator: Evaluator3D;
let wireframe: Mesh3D;

const params = {

    operation: SUBTRACTION,
    useGroups: true,
    wireframe: false,

};

init();
animate();

function init()
{
    // environment
    camera = new Node3D().addComponent('PerspectiveCamera3D', { fov: 50, aspect: window.innerWidth / window.innerHeight, near: 1, far: 100 });
    camera.entity.position.set(-1, 1, 1).normalize().multiplyScalar(10);

    scene = new Node3D().addComponent('Scene3D');
    scene.background = new Color(0xfce4ec);

    // lights
    const ambient = new Node3D().addComponent('HemisphereLight3D', { color: { value: 0xffffff }, groundColor: { value: 0xbfd4d2 }, intensity: 0.9 });
    scene.entity.addChild(ambient.entity);

    const directionalLight = new Node3D().addComponent('DirectionalLight3D', { color: { value: 0xffffff }, intensity: 0.1 });
    directionalLight.entity.position.set(1, 4, 3).multiplyScalar(3);
    directionalLight.castShadow = true;
    directionalLight.shadow.mapSize.setScalar(2048);
    directionalLight.shadow.bias = -1e-4;
    directionalLight.shadow.normalBias = 1e-4;
    scene.entity.addChild(directionalLight.entity);

    // renderer
    renderer = scene.addComponent('WebGLRenderer3D', { parameters: { antialias: true } });
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.shadowMap.enabled = true;
    renderer.shadowMap.type = 'PCFSoftShadowMap';
    document.body.appendChild(renderer.domElement);

    stats = scene.addComponent('StatsComponent');
    document.body.appendChild(stats.dom);

    // add shadow plane
    const plane = new Node3D().addComponent('Mesh3D', {
        geometry: new PlaneGeometry3D(),
        material: $set(new ShadowMaterial3D(), {
            color: { value: 0xd81b60 },
            transparent: true,
            opacity: 0.075,
            side: 'DoubleSide',
        }),
    });
    plane.entity.position.y = -3;
    plane.entity.rotation.x = -Math.PI / 2;
    plane.entity.scale.setScalar(10);
    plane.receiveShadow = true;
    scene.entity.addChild(plane.entity);

    // create brushes
    evaluator = new Evaluator3D();

    baseBrush = new Node3D().addComponent('Brush3D', {
        geometry: $set(new IcosahedronGeometry3D(), { radius: 2, detail: 3 }),
        material: $set(new MeshStandardMaterial3D(), {
            flatShading: true,

            polygonOffset: true,
            polygonOffsetUnits: 1,
            polygonOffsetFactor: 1,
        }),
    });

    brush = new Node3D().addComponent('Brush3D', {
        geometry: $set(new CylinderGeometry3D(), { radiusTop: 1, radiusBottom: 1, height: 5, radialSegments: 45 }),
        material: $set(new MeshStandardMaterial3D(), {
            color: { value: 0x80cbc4 },

            polygonOffset: true,
            polygonOffsetUnits: 1,
            polygonOffsetFactor: 1,

        }),
    });

    core = new Node3D().addComponent('Brush3D', {
        geometry: $set(new IcosahedronGeometry3D(), { radius: 0.15, detail: 1 }),
        material: $set(new MeshStandardMaterial3D(), {
            flatShading: true,
            color: { value: 0xff9800 },
            emissive: { value: 0xff9800 },
            emissiveIntensity: 0.35,

            polygonOffset: true,
            polygonOffsetUnits: 1,
            polygonOffsetFactor: 1,

        }),
    });
    core.castShadow = true;
    scene.entity.addChild(core.entity);

    // create wireframe
    wireframe = new Node3D().addComponent('Mesh3D', {
        material: $set(new MeshBasicMaterial3D(), { color: { value: 0x009688 }, wireframe: true }),
    });
    scene.entity.addChild(wireframe.entity);

    // controls
    const controls = scene.addComponent('OrbitControls3D', { camera, domElement: renderer.domElement });
    controls.minDistance = 5;
    controls.maxDistance = 75;

    // set up gui
    const gui = new GUI();
    gui.add(params, 'operation', { SUBTRACTION, INTERSECTION, ADDITION });
    gui.add(params, 'wireframe');
    gui.add(params, 'useGroups');

    window.addEventListener('resize', onWindowResize);
    onWindowResize();
}

function updateCSG()
{
    evaluator.useGroups = params.useGroups;
    result = evaluator.evaluate(baseBrush, brush, params.operation, result);

    result.castShadow = true;
    result.receiveShadow = true;
    scene.entity.addChild(result.entity);
}

function onWindowResize()
{
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    renderer.setSize(window.innerWidth, window.innerHeight);
}

function animate()
{
    requestAnimationFrame(animate);

    // update the transforms
    const t = window.performance.now() + 9000;
    baseBrush.entity.rotation.x = t * 0.0001;
    baseBrush.entity.rotation.y = t * 0.00025;
    baseBrush.entity.rotation.z = t * 0.0005;
    baseBrush.entity.updateMatrixWorld();

    brush.entity.rotation.x = t * -0.0002;
    brush.entity.rotation.y = t * -0.0005;
    brush.entity.rotation.z = t * -0.001;

    const s = 0.5 + 0.5 * (1 + Math.sin(t * 0.001));
    brush.entity.scale.set(s, 1, s);
    brush.entity.updateMatrixWorld();

    // update the csg
    updateCSG();

    wireframe.geometry = result.geometry;
    wireframe.entity.visible = params.wireframe;

    renderer.render(scene, camera);
    stats.update();
}
